/*
 *---------------------------------------------------------------------------
 *
 * Copyright (c) 2020, 2021 Analog Devices, Inc. All Rights Reserved.
 * This software is proprietary to Analog Devices, Inc.
 * and its licensors.By using this software you agree to the terms of the
 * associated Analog Devices Software License Agreement.
 *
 *---------------------------------------------------------------------------
 */

#include "main.h"

/* Number of frames to transmit. If 0, it runs continuously. */
#define FRAME_COUNT         (10000)
/* Frame size in bytes, applies to each of the transmitted frames. */
/* The value should be between 64 and 1518. */
#define FRAME_SIZE          (64)

#define ADIN1110_INIT_ITER  (5)

/* Application delay used to pace messages from reference device. */
#define SYNC_APP_DELAY_MS   (100)

/* Example configuration */
uint8_t devMem[ADIN1110_DEVICE_SIZE];

adin1110_DriverConfig_t drvConfig = {
    .pDevMem    = (void *)devMem,
    .devMemSize = sizeof(devMem),
    .fcsCheckEn = false,
};

/* Forward declarations */
void    printStats     (adin1110_DeviceHandle_t hDevice);
void    delay          (uint32_t ticks);

/* Shared timer configuration and format for both local and reference devices. */
adi_mac_TsFormat_e      tsFormat = ADI_MAC_TS_FORMAT_64B_1588;
adi_mac_TsTimerConfig_t timerConfig = {
        .periodNs       = 32000,
        .dutyCycle      = 0.5,
        .idleState      = false,
        .startTimeNs    = 160,
};

/* --------------------------------------------------------------------------------------------- */

/* Code to communicate with the Local device and sync it to the Reference device. */
#if defined(SYNC_LOCAL)

/* Value added to egress time when adjusting ingress counter to account for application delays and SPI access time. */
#define SYNC_ONE_MS_IN_NS           (1000000)
#define SYNC_FUTURE_OFFSET_NS       (SYNC_APP_DELAY_MS * SYNC_ONE_MS_IN_NS)
/* Number of iterations to try to achieve sync using addend adjustments. */
#define SYNC_ERROR_COUNT            (1000)
/* Threshold for error (in ns) between ingress and egress above which the clock will be adjusted directly. */
#define SYNC_SLAM_THRESHOLD_NS      (10000000)
/* Allowable error (in ns) between ingress and egress time to achieve sync. */
#define SYNC_PROPAGATION_DELAY_NS   (4850)
#define SYNC_ERROR_WINDOW_NS        (80 + SYNC_PROPAGATION_DELAY_NS)


volatile uint32_t               rxIdx = 0;
volatile bool                   rxDone = false;
volatile bool                   txDone = false;
volatile adi_eth_BufDesc_t      *pRxBufDesc;
uint32_t                        syncState = false;

/* Variables to track the synchronization process. */
syncStatistics_t syncStatistics;
syncMachineState_e syncMachineState = SYNC_MACHINE_IDLE;

/* Tx and Rx Buffers. */
HAL_ALIGNED_PRAGMA(4)
static uint8_t rxBuf[MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);
HAL_ALIGNED_PRAGMA(4)
static uint8_t txBuf[MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);

/* Forward declarations. */
adi_eth_Result_e SyncStateMachine(adin1110_DeviceHandle_t hDevice, adi_mac_TsTimespec_t *sync_IngressTime_p, adi_mac_TsTimespec_t *sync_EgressTime_p);
adi_eth_Result_e SyncStart(void);
adi_eth_Result_e SyncStop(void);

static void rxCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    /* Get the pointer to the Rx buffer descriptor. */
    pRxBufDesc = (adi_eth_BufDesc_t *)pArg;
    /* Set flag to indicate that the receive has completed. */
    rxDone = true;
    rxIdx++;
}

static void txCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    /* Set flag to indicate that the transmit has completed. */
    txDone = true;
}

int main(void)
{
    adi_eth_Result_e        result;
    uint32_t                error;
    adin1110_DeviceStruct_t dev;
    adin1110_DeviceHandle_t hDevice = &dev;

    /****** System Init *****/
    error = BSP_InitSystem();
    DEBUG_RESULT("BSP_InitSystem", error, 0);

    /* Hardware reset of the ADIN1110 using a host GPIO */
    BSP_HWReset(true);

    /****** Driver Init *****/
    for (uint32_t i = 0; i < ADIN1110_INIT_ITER; i++)
    {
        result = adin1110_Init(hDevice, &drvConfig);
        if (result == ADI_ETH_SUCCESS)
        {
            break;
        }
    }
    DEBUG_RESULT("No MACPHY device found", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[0][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[1][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    /* Timer block must be enabled and set to use the same timestamp format as the reference device. */
    result = adin1110_TsEnable(hDevice, tsFormat);
    DEBUG_RESULT("adin1110_TsEnable", result, ADI_ETH_SUCCESS);

    result = adin1110_SyncConfig(hDevice);
    DEBUG_RESULT("adin1110_SyncConfig", result, ADI_ETH_SUCCESS);

    adi_eth_BufDesc_t       rxBufDesc;
    adi_eth_BufDesc_t       txBufDesc;
    adi_eth_LinkStatus_e    linkStatus;
    uint32_t                heartbeatTicks;
    uint32_t                heartbeatCheckTime = 0;
    uint32_t                message;
    uint32_t                messageCount = 0;
    adi_mac_TsTimespec_t    egressTimestamp;
    adi_mac_TsTimespec_t    ingressTimestamp;
    bool                    syncRx = false;
    uint32_t                timestampL, timestampH;

    /* Prepare Rx buffer */
    rxBufDesc.pBuf    = rxBuf;
    rxBufDesc.bufSize = MAX_FRAME_BUF_SIZE;
    rxBufDesc.cbFunc  = rxCallback;
    result = adin1110_SubmitRxBuffer(hDevice, &rxBufDesc);
    /* Prepare Tx buffer */
    memcpy(txBuf, testFrame, FRAME_SIZE);

    result = adin1110_Enable(hDevice);
    DEBUG_RESULT("Device enable error", result, ADI_ETH_SUCCESS);

    /* Wait for link to be established */
    do
    {
        result = adin1110_GetLinkStatus(hDevice, &linkStatus);
        DEBUG_RESULT("adin1110_GetLinkStatus", result, ADI_ETH_SUCCESS);
    } while (linkStatus != ADI_ETH_LINK_STATUS_UP);

    /* Start the TS_TIMER waveform. */
    /* Synchronization will pull this waveform in line with the link partner (reference) waveform. */
    result = adin1110_TsTimerStart(hDevice, &timerConfig);

    result = SyncStart();
    while (1)
    {
        uint32_t now  = BSP_SysNow();

        /* Heartbeat pulse approximately once every 250ms. */
        if (now - heartbeatCheckTime >= 250)
        {
            heartbeatCheckTime = now;
            BSP_HeartBeat();
            heartbeatTicks++;
        }

        /* If we have synchronized, send a message to the reference to restart the timer waveform. */
        if (syncState && !txDone)
        {
            txBufDesc.pBuf    = txBuf;
            txBufDesc.trxSize = FRAME_SIZE;
            txBufDesc.bufSize = MAX_FRAME_BUF_SIZE;
            txBufDesc.cbFunc = txCallback;

            message = RESP_MESSAGE;
            memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX], &message, 4);
            /* Setting the waveform start time to the last ingress timestamp should give us enough time to notify the reference device. */
            timerConfig.startTimeNs = ingressTimestamp.nsec;
            memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX + 4], &timerConfig.startTimeNs, 4);

            result = adin1110_SubmitTxBuffer(hDevice, &txBufDesc);
            delay(SYNC_APP_DELAY_MS);
            result = adin1110_TsTimerStop(hDevice);
            result = adin1110_TsTimerStart(hDevice, &timerConfig);

            /* We could stop the state machine here, but let's keep it running in case we lose sync. */
            // result = SyncStop();
        }

        /* Only try to process a frame after the rxCallback has executed and set pRxBufDesc. */
        if (rxDone)
        {
            rxDone = false;
            memcpy(&message, &pRxBufDesc->pBuf[ETH_PAYLOAD_IDX], 4);
            if (message == SYNC_MESSAGE)
            {
                /* Register ingress timestamp (T2) from SYNC message. */
                timestampL = pRxBufDesc->timestamp;
                timestampH = pRxBufDesc->timestampExt;
                result = adin1110_TsConvert(timestampL, timestampH, hDevice->pMacDevice->timestampFormat, &ingressTimestamp);
                syncRx = true;
            }
            else if (message == FWUP_MESSAGE)
            {
                /* FOLLOW-UP message contains egress timestamp (T1) of SYNC message. */
                memcpy(&egressTimestamp.sec, &pRxBufDesc->pBuf[ETH_PAYLOAD_IDX + 4], 4);
                memcpy(&egressTimestamp.nsec, &pRxBufDesc->pBuf[ETH_PAYLOAD_IDX + 8], 4);

                if (syncRx)
                {
                    /* SYNC & FOLLOW-UP messages received: process the new sample. */
                    syncStatistics.egressTime.sec  = egressTimestamp.sec;
                    syncStatistics.egressTime.nsec = egressTimestamp.nsec;
                    syncStatistics.ingressTime.sec   = ingressTimestamp.sec;
                    syncStatistics.ingressTime.nsec  = ingressTimestamp.nsec;
                    result = SyncStateMachine(hDevice, &ingressTimestamp, &egressTimestamp);
                    syncRx = false;
                    if (messageCount == 0)
                    {
                        printStats(hDevice);
                    }
                    messageCount++;
                }
            }

            /* We are not doing anything with the Rx buffer here, so re-submit it to the queue. */
            result = adin1110_SubmitRxBuffer(hDevice, (adi_eth_BufDesc_t *)pRxBufDesc);
        }

        /* Periodically print the statistics over the serial port. */
        if (heartbeatTicks >= 8)
        {
            heartbeatTicks = 0;
            printStats(hDevice);
        }
    }
}

/* Initializes and starts the state machine. */
adi_eth_Result_e SyncStart(void)
{
    syncStatistics.slamCounter = 0;
    syncStatistics.syncCounter = 0;
    syncStatistics.syncCycleCounter = 0;

    syncMachineState = SYNC_MACHINE_WAIT;

    return ADI_ETH_SUCCESS;
}

/* Stops the state machine. */
adi_eth_Result_e SyncStop(void)
{
    syncMachineState = SYNC_MACHINE_IDLE;
    return ADI_ETH_SUCCESS;
}

/* State Machine is called whenever a FOLLOW_UP message is received after a SYNC message.
 * Assumes that the FOLLOW_UP message contains the egress timestamp of the SYNC message.
 */
adi_eth_Result_e SyncStateMachine(adin1110_DeviceHandle_t hDevice, adi_mac_TsTimespec_t *pIngressTime, adi_mac_TsTimespec_t *pEgressTime)
{
    adi_eth_Result_e result = ADI_ETH_SUCCESS;
    int64_t tError;
    uint64_t utError;

    static adi_mac_TsTimespec_t egressPrev;
    static adi_mac_TsTimespec_t ingressPrev;
    static adi_mac_TsTimespec_t tOffset;

    /* Calculate signed error and magnitude. */
    tError = adin1110_TsSubtract(pEgressTime, pIngressTime);
    utError = (uint64_t)abs((int32_t)tError);

    switch (syncMachineState)
    {
        case SYNC_MACHINE_IDLE:
            return result;
            break;

        case SYNC_MACHINE_WAIT:
            /* After receiving SYNC & FOLLOW-UP messages, try to set local to reference time plus some offset to account for application delays. */
            syncStatistics.slamCounter++;
            tOffset.nsec = pEgressTime->nsec + SYNC_FUTURE_OFFSET_NS;
            /* After adding offset, we need to make sure that nsec is not greater than one sec: carry into sec as needed. */
            if (tOffset.nsec >= ADI_MAC_TS_ONE_SECOND_IN_NS)
            {
                tOffset.sec = pEgressTime->sec + 1;
                tOffset.nsec -= ADI_MAC_TS_ONE_SECOND_IN_NS;
            }
            else
            {
                tOffset.sec = pEgressTime->sec;
            }
            /* Slam the clock to try to get the counters close enough to sync with fine adjustment. */
            adin1110_TsSetTimerAbsolute(hDevice, tOffset.sec, tOffset.nsec);
            syncMachineState = SYNC_MACHINE_ASYNC;
            txDone = false;
            return result;
            break;

        case SYNC_MACHINE_ASYNC:
            /* If the error is small enough, we are synced. */
            if (utError < SYNC_ERROR_WINDOW_NS)
            {
                syncMachineState = SYNC_MACHINE_SYNCED;
                syncStatistics.syncCycleCounter = syncStatistics.syncCounter;
                syncStatistics.syncCounter = 0;
            }
            else
            {
                syncStatistics.syncCounter++;
                /* If the error is not too large, we can try to adjust using the addend. */
                /* If adjusting the addend does not let us sync, we need to slam again. */
                if ((utError > SYNC_SLAM_THRESHOLD_NS) || (syncStatistics.syncCounter > SYNC_ERROR_COUNT))
                {
                    syncState = false;
                    syncStatistics.syncCounter = 0;
                    syncMachineState = SYNC_MACHINE_WAIT;
                    return result;
                }
            }
            break;

        case SYNC_MACHINE_SYNCED:
            /* Check to see if we somehow lost sync. */
            if (utError > SYNC_ERROR_WINDOW_NS)
            {
                syncStatistics.syncCounter++;
                /* If the error is not too large, we can try to adjust using the addend. */
                /* If adjusting the addend does not let us sync, we need to slam again. */
                if ((utError > SYNC_SLAM_THRESHOLD_NS) || (syncStatistics.syncCounter > SYNC_ERROR_COUNT))
                {
                    syncState = false;
                    syncStatistics.syncCounter = 0;
                    syncMachineState = SYNC_MACHINE_WAIT;
                    return result;
                }
            }
            else
            {
                /* Error was small enough, so we are synced. */
                syncStatistics.syncCounter = 0;
                syncState = true;
            }
            break;

        default:
            break;
    }
    /* Adjust the addend for a small frequency shift. */
    /* This line will run unless we are going back to SYNC_MACHINE_WAIT. */
    result = adin1110_TsSyncClock(hDevice, tError + SYNC_PROPAGATION_DELAY_NS,
                                  (uint64_t)adin1110_TsSubtract(pEgressTime, &egressPrev),
                                  (uint64_t)adin1110_TsSubtract(pIngressTime, &ingressPrev));

    /* Store values for the next iteration. */
    egressPrev.sec  = pEgressTime->sec;
    egressPrev.nsec = pEgressTime->nsec;
    ingressPrev.sec   = pIngressTime->sec;
    ingressPrev.nsec  = pIngressTime->nsec;

    /* Update statistics values. */
    syncStatistics.timeOffset = tError;
    syncStatistics.syncState = syncState;
    adin1110_ReadRegister(hDevice, ADDR_MAC_TS_ADDEND, &syncStatistics.addend);

    return result;
}

/* Print out some statistics about the sync process. */
void printStats(adin1110_DeviceHandle_t hDevice)
{
    DEBUG_MESSAGE("Egress Time: %ds %dns\r", syncStatistics.egressTime.sec, syncStatistics.egressTime.nsec);
    DEBUG_MESSAGE("Ingress Time: %ds %dns\r", syncStatistics.ingressTime.sec, syncStatistics.ingressTime.nsec);
    DEBUG_MESSAGE("Slam Counter: %d\r", syncStatistics.slamCounter);
    DEBUG_MESSAGE("Sync Counter: %d\r", syncStatistics.syncCounter);
    DEBUG_MESSAGE("Sync State: %d\r", syncStatistics.syncState);
    DEBUG_MESSAGE("Cycles to Sync: %d\r", syncStatistics.syncCycleCounter);
    DEBUG_MESSAGE("Addend: 0x%08X\r", syncStatistics.addend);
    DEBUG_MESSAGE("Time Offset: %d\r", syncStatistics.timeOffset);
    DEBUG_MESSAGE("Time Offset + Propagation Delay: %d\r", syncStatistics.timeOffset + SYNC_PROPAGATION_DELAY_NS);
    DEBUG_MESSAGE("--------------------------\r");
}

#endif // SYNC_LOCAL

/* Code to communicate with the Reference device to which the Local device will sync. */
#if defined(SYNC_REFERENCE)

volatile uint32_t               txIdx = 0;
volatile bool                   txDone = false;
volatile bool                   timestampCaptured = false;
volatile bool                   rxDone = false;
volatile adi_eth_BufDesc_t      *pRxBufDesc;

/* Tx and Rx Buffers. */
HAL_ALIGNED_PRAGMA(4)
static uint8_t txBuf[MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);
HAL_ALIGNED_PRAGMA(4)
static uint8_t rxBuf[MAX_FRAME_BUF_SIZE] HAL_ALIGNED_ATTRIBUTE(4);

static void txCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    /* Set flag to indicate that the transmit has completed. */
    txDone = true;
    txIdx++;
}

static void rxCallback(void *pCBParam, uint32_t Event, void *pArg)
{
    /* Get the pointer to the Rx buffer descriptor. */
    pRxBufDesc = (adi_eth_BufDesc_t *)pArg;
    /* Set flag to indicate that the receive has completed. */
    rxDone = true;
}

void cbTimestamp(void *pCBParam, uint32_t Event, void *pArg)
{
    adi_mac_TimestampRdy_t  timestampReady;

    timestampReady = *(adi_mac_TimestampRdy_t *)pArg;

    /* We are only using egress register A. */
    if (timestampReady.timestampReadyA)
    {
        /* Let the main loop know it can read the timestamp and send the follow-up message. */
        timestampCaptured = true;
    }
}

int main(void)
{
    adi_eth_Result_e        result;
    uint32_t                error;
    adin1110_DeviceStruct_t dev;
    adin1110_DeviceHandle_t hDevice = &dev;

    /****** System Init *****/
    error = BSP_InitSystem();
    DEBUG_RESULT("BSP_InitSystem", error, 0);

    /* Hardware reset of the ADIN1110 using a host GPIO */
    BSP_HWReset(true);

    /****** Driver Init *****/
    for (uint32_t i = 0; i < ADIN1110_INIT_ITER; i++)
    {
        result = adin1110_Init(hDevice, &drvConfig);
        if (result == ADI_ETH_SUCCESS)
        {
            break;
        }
    }
    DEBUG_RESULT("No MACPHY device found", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[0][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    result = adin1110_AddAddressFilter(hDevice, &macAddr[1][0], NULL, 0);
    DEBUG_RESULT("adin1110_AddAddressFilter", result, ADI_ETH_SUCCESS);

    /* Timer block must be enabled and set to use the same timestamp format as the local device. */
    result = adin1110_TsEnable(hDevice, tsFormat);
    DEBUG_RESULT("adin1110_TsEnable", result, ADI_ETH_SUCCESS);

    result = adin1110_SyncConfig(hDevice);
    DEBUG_RESULT("adin1110_SyncConfig", result, ADI_ETH_SUCCESS);

    result = adin1110_RegisterCallback(hDevice, cbTimestamp, ADI_MAC_EVT_TIMESTAMP_RDY);
    DEBUG_RESULT("adin1110_RegisterCallback (ADI_MAC_EVT_TIMESTAMP_RDY)", result, ADI_ETH_SUCCESS);

    adi_eth_BufDesc_t       txBufDesc;
    adi_eth_BufDesc_t       rxBufDesc;
    adi_eth_LinkStatus_e    linkStatus;
    uint32_t                frameIdx;
    uint32_t                heartbeatTicks;
    uint32_t                heartbeatCheckTime = 0;
    uint32_t                message;
    adi_mac_TsTimespec_t    egressTimestamp;
    bool                    syncTx = false;

    /* Prepare Tx buffer */
    memcpy(txBuf, testFrame, FRAME_SIZE);
    /* Prepare Rx buffer */
    rxBufDesc.pBuf    = rxBuf;
    rxBufDesc.bufSize = MAX_FRAME_BUF_SIZE;
    rxBufDesc.cbFunc  = rxCallback;
    result = adin1110_SubmitRxBuffer(hDevice, &rxBufDesc);

    result = adin1110_Enable(hDevice);
    DEBUG_RESULT("Device enable error", result, ADI_ETH_SUCCESS);

    /* Wait for link to be established */
    do
    {
        result = adin1110_GetLinkStatus(hDevice, &linkStatus);
        DEBUG_RESULT("adin1110_GetLinkStatus", result, ADI_ETH_SUCCESS);
    } while (linkStatus != ADI_ETH_LINK_STATUS_UP);

    frameIdx = 0;
    heartbeatTicks = 0;

    /* Start the TS_TIMER waveform. */
    /* Synchronization will pull the link partner (local) TS_TIMER waveform in line with this waveform. */
    result = adin1110_TsTimerStart(hDevice, &timerConfig);

    while (1)
    {
        uint32_t now  = BSP_SysNow();

        /* Heartbeat pulse approximately once every 250ms. */
        if (now - heartbeatCheckTime >= 250)
        {
            heartbeatCheckTime = now;
            BSP_HeartBeat();
            heartbeatTicks++;
        }

        /* Only try to process a frame after the rxCallback has executed and set pRxBufDesc. */
        if (rxDone)
        {
            rxDone = false;
            memcpy(&message, &pRxBufDesc->pBuf[ETH_PAYLOAD_IDX], 4);
            if (message == RESP_MESSAGE)
            {
                /* RESPONSE message contains the new start time for the timer waveform. */
                memcpy(&timerConfig.startTimeNs, &pRxBufDesc->pBuf[ETH_PAYLOAD_IDX + 4], 4);
                result = adin1110_TsTimerStop(hDevice);
                result = adin1110_TsTimerStart(hDevice, &timerConfig);
            }
            /* We are not doing anything with the Rx buffer here, so re-submit it to the queue. */
            result = adin1110_SubmitRxBuffer(hDevice, (adi_eth_BufDesc_t *)pRxBufDesc);
        }


        /* Alternate sending sync and follow-up messages with delays. */
        if ((frameIdx == 0) || (txDone))
        {
            txDone = false;
            txBufDesc.pBuf    = txBuf;
            txBufDesc.trxSize = FRAME_SIZE;
            txBufDesc.bufSize = MAX_FRAME_BUF_SIZE;
            txBufDesc.cbFunc = txCallback;

            if (timestampCaptured)
            {
                txBufDesc.egressCapt = ADI_MAC_EGRESS_CAPTURE_NONE;

                /* FOLLOW-UP message must contain the egress timestamp (T1) from the SYNC message. */
                result = adin1110_TsGetEgressTimestamp(hDevice, ADI_MAC_EGRESS_CAPTURE_A, &egressTimestamp);
                message = FWUP_MESSAGE;
                memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX], &message, 4);
                memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX + 4], &egressTimestamp.sec, 4);
                memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX + 8], &egressTimestamp.nsec, 4);
                timestampCaptured = false;
                syncTx = false;
            }
            else
            {
                if(!syncTx)
                {
                    txBufDesc.egressCapt = ADI_MAC_EGRESS_CAPTURE_A;

                    /* SYNC message does not need to contain any particular data. */
                    /* Local partner will register ingress timstamp (T2) of SYNC message. */
                    message = SYNC_MESSAGE;
                    memcpy(&txBufDesc.pBuf[ETH_PAYLOAD_IDX], &message, 4);
                    syncTx = true;
                }
            }

            result = adin1110_SubmitTxBuffer(hDevice, &txBufDesc);
            if (result == ADI_ETH_SUCCESS)
            {
                frameIdx++;
            }

            /* Delay: no need to flood the partner with messages. */
            delay(SYNC_APP_DELAY_MS);
        }
    }
}

#endif // SYNC_REFERENCE

/* --------------------------------------------------------------------------------------------- */

/* Delay for the specified number of milliseconds. */
void delay(uint32_t msTicks)
{
    uint32_t startTime = BSP_SysNow();
    while ((BSP_SysNow() - startTime)  < msTicks)
    ;
}
